﻿Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Security
Imports System.Net.Sockets
Imports System.Text

Namespace GoogleAnalyticsMailHandler
    Public Class Pop3Client
        Implements IDisposable

        Public Property Host() As String
            Get
                Return m_Host
            End Get
            Protected Set(ByVal value As String)
                m_Host = value
            End Set
        End Property
        Private m_Host As String
        Public Property Port() As Integer
            Get
                Return m_Port
            End Get
            Protected Set(ByVal value As Integer)
                m_Port = value
            End Set
        End Property
        Private m_Port As Integer
        Public Property Email() As String
            Get
                Return m_Email
            End Get
            Protected Set(ByVal value As String)
                m_Email = value
            End Set
        End Property
        Private m_Email As String
        Public Property Password() As String
            Get
                Return m_Password
            End Get
            Protected Set(ByVal value As String)
                m_Password = value
            End Set
        End Property
        Private m_Password As String
        Public Property IsSecure() As Boolean
            Get
                Return m_IsSecure
            End Get
            Protected Set(ByVal value As Boolean)
                m_IsSecure = value
            End Set
        End Property
        Private m_IsSecure As Boolean
        Public Property Client() As TcpClient
            Get
                Return m_Client
            End Get
            Protected Set(ByVal value As TcpClient)
                m_Client = value
            End Set
        End Property
        Private m_Client As TcpClient
        Public Property ClientStream() As Stream
            Get
                Return m_ClientStream
            End Get
            Protected Set(ByVal value As Stream)
                m_ClientStream = value
            End Set
        End Property
        Private m_ClientStream As Stream
        Public Property Writer() As StreamWriter
            Get
                Return m_Writer
            End Get
            Protected Set(ByVal value As StreamWriter)
                m_Writer = value
            End Set
        End Property
        Private m_Writer As StreamWriter
        Public Property Reader() As StreamReader
            Get
                Return m_Reader
            End Get
            Protected Set(ByVal value As StreamReader)
                m_Reader = value
            End Set
        End Property
        Private m_Reader As StreamReader
        Private disposed As Boolean = False
        Public Sub New(ByVal host As String, ByVal port As Integer, ByVal email As String, ByVal password As String)
            Me.New(host, port, email, password, False)
        End Sub

        Public Sub New(ByVal hostParam As String, ByVal portParam As Integer, ByVal emailParam As String, ByVal passwordParam As String, ByVal secure As Boolean)
            Host = hostParam
            Port = portParam
            Email = emailParam
            Password = passwordParam
            IsSecure = secure
        End Sub

        Public Sub Connect()
            If Client Is Nothing Then
                Client = New TcpClient()
            End If
            If Not Client.Connected Then
                Client.Connect(Host, Port)
            End If
            If IsSecure Then
                Dim secureStream As New SslStream(Client.GetStream())
                secureStream.AuthenticateAsClient(Host)
                ClientStream = secureStream
                secureStream = Nothing
            Else
                ClientStream = Client.GetStream()
            End If
            Writer = New StreamWriter(ClientStream)
            Reader = New StreamReader(ClientStream)
            ReadLine()
            Login()
        End Sub

        Public Function GetEmailCount() As Integer
            Dim count As Integer = 0
            Dim response As String = SendCommand("STAT")
            If IsResponseOk(response) Then
                Dim arr As String() = response.Substring(4).Split(" "c)
                count = Convert.ToInt32(arr(0))
            Else
                count = -1
            End If
            Return count
        End Function

        Public Function FetchEmail(ByVal emailId As Integer) As Email
            If IsResponseOk(SendCommand("TOP " & emailId.ToString() & " 0")) Then
                Return New Email(ReadLines(), emailId)
            Else
                Return Nothing
            End If
        End Function

        Public Function FetchEmailList(ByVal start As Integer, ByVal count As Integer) As List(Of Email)
            Dim emails As New List(Of Email)(count)
            For i As Integer = start To (start + count) - 1
                Dim email As Email = FetchEmail(i)
                If email IsNot Nothing Then
                    emails.Add(email)
                End If
            Next
            Return emails
        End Function

        Public Function FetchMessageParts(ByVal emailId As Integer) As List(Of MessagePart)
            If IsResponseOk(SendCommand("RETR " & emailId.ToString())) Then
                Return Util.ParseMessageParts(ReadLines())
            End If

            Return Nothing
        End Function

        Public Sub Close()
            If Client IsNot Nothing Then
                If Client.Connected Then
                    Logout()
                End If
                Client.Close()
                Client = Nothing
            End If
            If ClientStream IsNot Nothing Then
                ClientStream.Close()
                ClientStream = Nothing
            End If
            If Writer IsNot Nothing Then
                Writer.Close()
                Writer = Nothing
            End If
            If Reader IsNot Nothing Then
                Reader.Close()
                Reader = Nothing
            End If
            disposed = True
        End Sub

        Public Overloads Sub Dispose() Implements IDisposable.Dispose
            If Not disposed Then
                Close()
            End If
        End Sub

        Protected Sub Login()
            If Not IsResponseOk(SendCommand("USER " & Email)) OrElse Not IsResponseOk(SendCommand("PASS " & Password)) Then
                Throw New Exception("User/password not accepted")
            End If
        End Sub

        Protected Sub Logout()
            IsResponseOk(SendCommand("QUIT"))
        End Sub

        Protected Function SendCommand(ByVal cmdtext As String) As String
            Writer.WriteLine(cmdtext)
            Writer.Flush()
            Return ReadLine()
        End Function

        Protected Function ReadLine() As String
            Return Reader.ReadLine() + vbCr & vbLf
        End Function

        Protected Function ReadLines() As String
            Dim b As New StringBuilder()
            While True
                Dim temp As String = ReadLine()
                If temp = "." & vbCr & vbLf OrElse temp.IndexOf("-ERR") <> -1 Then
                    Exit While
                End If
                b.Append(temp)
            End While
            Return b.ToString()
        End Function

        Protected Shared Function IsResponseOk(ByVal response As String) As Boolean
            If response.StartsWith("+OK") Then
                Return True
            End If
            If response.StartsWith("-ERR") Then
                Return False
            End If
            Throw New Exception("Cannot understand server response: " + response)
        End Function

        Public Function DeleteEmail(ByVal emailId As Integer) As Boolean
            Return IsResponseOk(SendCommand("DELE " & emailId.ToString()))
        End Function

    End Class
End Namespace

